var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { walk } from 'estree-walker';
import matter from 'gray-matter';
import { remark } from 'remark';
import { visit } from 'unist-util-visit';
import { coreRemarkMdxPlugins, getAST } from '../../remark.js';
import { createUniqueVariableName, isMdxJsxFlowElement } from '../../utils.js';
import { findAndRemoveExports } from '../findAndRemoveExports.js';
import { injectToTopOfFile } from './injectToTopOfFile.js';
/**
 *
 * @param content The content to inject into
 * @param componentName The component name we are injecting into
 * @param contentToInject The content to inject in place of the component
 */
export const resolveComponentWithContent = (content, componentName, snippet, exportMap) => __awaiter(void 0, void 0, void 0, function* () {
    const { content: snippetWithoutFrontmatter } = matter(snippet);
    const { exportMap: snippetExportMap, content: snippetWithoutExports } = yield findAndRemoveExports(snippetWithoutFrontmatter);
    const snippetAST = getAST(snippetWithoutExports);
    const convertedContent = yield remark()
        .use(coreRemarkMdxPlugins)
        .use(remarkMdxResolveComponentWithContent(snippetAST, componentName, exportMap, snippetExportMap))
        .process(content);
    const convertedContentStr = String(convertedContent);
    return reinsertExports(convertedContentStr, exportMap, snippetExportMap);
});
/**
 * Everywhere where there is a component with the name `componentName`, replace it with the `treeToInject`.
 * New variables are also exported to be used for the variables. Additional mutations are made to support duplicated snippets
 *
 * This function is used to resolve default imports
 * @param treeToInject
 * @param componentName
 * @returns
 */
const remarkMdxResolveComponentWithContent = (treeToInject, componentName, exportMap, snippetExportMap) => () => (tree) => {
    visit(tree, isMdxJsxFlowElement, (node, i, parent) => {
        var _a;
        if (node.name === componentName && parent && i != null) {
            // Creating clone to restore treeToInject with default values at the end of operations
            const treeToInjectClone = structuredClone(treeToInject);
            node.name = null;
            const { variableDeclarationNodes, snippetNodesToInject } = replaceVariablesWithProps(node, treeToInject, exportMap, snippetExportMap);
            parent.children.splice(i, 1, ...snippetNodesToInject);
            tree.children.splice(((_a = tree.children[0]) === null || _a === void 0 ? void 0 : _a.type) === 'yaml' ? 1 : 0, 0, ...variableDeclarationNodes);
            treeToInject = treeToInjectClone;
        }
    });
};
/**
 * Replace the variables with value provided from the props by defining the variable before the snippet
 * Also prevents duplicates by attaching a unique identifier to every variable (var to var_1)
 *
 * @param node
 * @param treeToInject
 * @returns
 */
const replaceVariablesWithProps = (node, treeToInject, exportMap, snippetExportMap) => {
    const variableNameMap = {};
    const variablesMap = {};
    visit(treeToInject, (node) => {
        var _a;
        if ((node.type === 'mdxTextExpression' || node.type === 'mdxFlowExpression') &&
            ((_a = node.data) === null || _a === void 0 ? void 0 : _a.estree)) {
            node.data.estree = walk(node.data.estree, {
                enter(jsNode) {
                    if (jsNode.type === 'Identifier' && snippetExportMap[jsNode.name] == undefined) {
                        let id = 0;
                        let uniqueName = createUniqueVariableName(jsNode.name, id);
                        while (exportMap[uniqueName]) {
                            id += 1;
                            uniqueName = createUniqueVariableName(jsNode.name, id);
                        }
                        // Replace node value with unique name
                        node.value = node.value.replace(jsNode.name, uniqueName);
                        variablesMap[uniqueName] = { type: 'unknown', value: 'undefined' };
                        variableNameMap[jsNode.name] = uniqueName;
                        jsNode.name = uniqueName;
                    }
                },
            });
        }
    });
    node.attributes.forEach((attribute) => {
        if (attribute.type === 'mdxJsxAttribute' && attribute.value) {
            const uniqueName = variableNameMap[attribute.name];
            if (uniqueName != undefined) {
                variablesMap[uniqueName] =
                    typeof attribute.value === 'string'
                        ? { type: 'string', value: attribute.value }
                        : { type: 'unknown', value: attribute.value.value };
            }
        }
    });
    const variableDeclarationNodes = [];
    Object.entries(variablesMap).map(([variable, value]) => {
        const statement = `export const ${variable} = ${value.type === 'string' ? `"${value.value}"` : value.value}`;
        exportMap[variable] = statement;
        variableDeclarationNodes.push(...getAST(statement).children);
    });
    return {
        variableDeclarationNodes,
        snippetNodesToInject: treeToInject.children,
    };
};
const reinsertExports = (content, exportMap, snippetExportMap) => {
    for (const [key, value] of Object.entries(snippetExportMap)) {
        // TODO: handle duplicate exports
        if (exportMap[key] == undefined) {
            content = injectToTopOfFile(content, value);
            exportMap[key] = value;
        }
    }
    return content;
};
